// This program is a trivial little flight simulator.  You control a ship

// with the keyboard.  Use

//

//   J and L keys to roll,

//   I and K keys to pitch,

//   H and ; keys to yaw,

//   8 key to increase speed and the M key to decrease speed.

//   W key toggles wireframe mode

//   R key generates a new landscape

//

// In this little program you fly around a single fractal landscape. It would

// be best to extend the program so that one could plug in any arbitrary

// scene.



#include <GL/glut.h>

#include "cockpit.h"

#include "landscape.h"

#include <iostream>
#include "glm.h"

#include "tgaload.h"

#define CUBE_0 0

#define CUBE_1 1

#define CUBE_2 2

#define CUBE_3 3

#define CUBE_4 4

#define CUBE_5 5



#define TERMINATOR 0

#define INTRUDER 1

#define BABYLON 2

// You need to declare your self a pointer to the model
// This is where the data will be loaded
GLMmodel* pmodel1 = NULL;
// vetor com os números das texturas
GLuint texture_cube[6], texture_plane[3];
static bool firstPerson = true;
void initTexture (void) {
	image_t intruder, terminator, babylon, cube; // variável que irá armazenar a textura a ser usada

	// Habilita o uso de textura 
	glEnable ( GL_TEXTURE_2D );

	// Define a forma de armazenamento dos pixels na textura (1= alihamento por byte)
	glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 );

	// Define quantas texturas serão usadas no programa 
	glGenTextures (3, texture_plane);
	glGenTextures (6, texture_cube);

	// Define os números da textura dos cubos
	texture_cube[CUBE_0] = 0;
	texture_cube[CUBE_1] = 1;
	texture_cube[CUBE_2] = 2;
	texture_cube[CUBE_3] = 3;
	texture_cube[CUBE_4] = 4;
	texture_cube[CUBE_5] = 5;
	
	texture_plane[TERMINATOR] = 6;
	texture_plane[INTRUDER] = 7;
	texture_plane[BABYLON] = 8;
	
	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_0] );
	tgaLoad  ( "Cube/sky0.tga", &cube, TGA_NO_MIPMAPS  );
	tgaSetTexParams();
	
	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_1] );
	tgaLoad  ( "Cube/sky3.tga", &cube, TGA_NO_MIPMAPS );
	tgaSetTexParams();

	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_2] );
	tgaLoad  ( "Cube/sky5.tga", &cube, TGA_NO_MIPMAPS  );
	tgaSetTexParams();
	
	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_3] );
	tgaLoad  ( "Cube/sky2.tga", &cube, TGA_NO_MIPMAPS  );
	tgaSetTexParams();

	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_4] );
	tgaLoad  ( "Cube/sky4.tga", &cube, TGA_NO_MIPMAPS  );
	tgaSetTexParams();
	
	glBindTexture ( GL_TEXTURE_2D, texture_cube[CUBE_5] );
	tgaLoad  ( "Cube/sky1.tga", &cube, TGA_NO_MIPMAPS );
	tgaSetTexParams();
	
	glBindTexture ( GL_TEXTURE_2D, texture_plane[TERMINATOR] );
	tgaLoad  ( "Terminator/Terminator.tga", &terminator, TGA_DEFAULT );
	
	glBindTexture ( GL_TEXTURE_2D, texture_plane[INTRUDER] );
	tgaLoad  ( "Intruder/Intruder.tga", &intruder, TGA_DEFAULT );

	glBindTexture ( GL_TEXTURE_2D, texture_plane[BABYLON] );
	tgaLoad  ( "Babylon/Babylon.tga", &babylon, TGA_DEFAULT );
}

void drawTerminator(void){
    glBindTexture ( GL_TEXTURE_2D, texture_plane[TERMINATOR] );
	// Load the model only if it hasn't been loaded before
	// If it's been loaded then pmodel1 should be a pointer to the model geometry data...otherwise it's null
    if (!pmodel1){
		// this is the call that actualy reads the OBJ and creates the model object
        pmodel1 = glmReadOBJ("Terminator/Terminator.obj");	
	
        if (!pmodel1) exit(0);
		// This will rescale the object to fit into the unity matrix
		// Depending on your project you might want to keep the original size and positions you had in 3DS Max or GMAX so you may have to comment this.
        glmUnitize(pmodel1);
		// These 2 functions calculate triangle and vertex normals from the geometry data.
		// To be honest I had some problem with very complex models that didn't look to good because of how vertex normals were calculated
		// So if you can export these directly from you modeling tool do it and comment these line
		// 3DS Max can calculate these for you and GLM is perfectly capable of loading them
        glmFacetNormals(pmodel1);        
		glmVertexNormals(pmodel1, 1.0);
	
    }
    // This is the call that will actualy draw the model
    // Don't forget to tell it if you want textures or not :))
    glmDraw(pmodel1, GLM_SMOOTH | GLM_TEXTURE); // | GLM_TEXTURE);
}


// A landscape to fly around, with some parameters that are manipuated by the

// program.

Landscape landscape(200, 143);



// Wireframe view or solid view?

static bool wireframe = false;



void newLandscape() {

  static double rug = ((double)rand()) / RAND_MAX;

  landscape.create(rug);

}



// A ship and some functions to control it: Later, we need to add a ship

// controller class so even the navigation controls are pluggable.

static Ship theShip(Point(60, 40, 220));

static Cockpit cockpit(theShip);



void keyboard(unsigned char key, int, int) {

  const double deltaSpeed = 0.01;

  const double angle = 0.02;

  switch(key) {

    case '8': theShip.setSpeed(theShip.getSpeed() + deltaSpeed); break;

    case 'm': theShip.setSpeed(theShip.getSpeed() - deltaSpeed); break;

    case 'w': wireframe = !wireframe; break;

    case 'r': newLandscape();

    case 'j': theShip.roll(angle); break;

    case 'l': theShip.roll(-angle); break;

    case 'h': theShip.yaw(angle); break;

    case ';': theShip.yaw(-angle); break;

    case 'i': theShip.pitch(-angle); break;

    case 'k': theShip.pitch(angle);  break;
	case 'c': if(firstPerson == true) firstPerson = false;
				else firstPerson = true;
			break;

  }

}



// Display and Animation: To draw we just clear the window and draw the scene.

// Because our main window is double buffered we have to swap the buffers to

// make the drawing visible.  Animation is achieved by successively moving

// the ship and drawing.

void display() {

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  wireframe ? landscape.drawWireFrame() : landscape.draw();

  if(firstPerson) {
	cockpit.draw();
  } 
  else {
  	glColor3f(1.0f, 1.0f, 1.0f);

	// Desenha o teapot com a cor corrente (solid)
	//glutSolidTeapot(50.0f);
	Point temp(theShip.getPosition() + theShip.getDirection());
	glTranslatef(temp.x, temp.y, temp.z);
	glRotatef(-90, 1, 0, 0);
	drawTerminator();
  }
  glFlush();

  glutSwapBuffers();

}

static Point eye(theShip.getPosition());

static Point at(theShip.getPosition() + theShip.getDirection());

static Vector up(theShip.getVertical());

// Move the ship one step, recompute the view, and ask to redisplay.

void timer(int v) {
if(firstPerson){

  theShip.fly();
}

  Point eye(theShip.getPosition());

  Point at(theShip.getPosition() + theShip.getDirection());

  Vector up(theShip.getVertical());

  glLoadIdentity();

  gluLookAt(eye.x, eye.y, eye.z, at.x, at.y, at.z, up.i, up.j, up.k);
glutPostRedisplay();

  glutTimerFunc(1000/60, timer, v);

}



// Reshape callback: Make the viewport take up the whole window, recompute the

// camera settings to match the new window shape, and go back to modelview

// matrix mode.

void reshape(int w, int h) {

  glViewport(0, 0, w, h);

  glMatrixMode(GL_PROJECTION);

  glLoadIdentity();

  gluPerspective(60.0, (GLfloat)w/(GLfloat)h, 0.05, 300.0);

  glMatrixMode(GL_MODELVIEW);

}



// init(): Initialize GLUT and enter the GLUT event loop.

void init() {

  srand(9903);

  glEnable(GL_DEPTH_TEST);

  newLandscape();

  cockpit.create();

  GLfloat black[] = { 0.0, 0.0, 0.0, 1.0 };

  GLfloat dark[] = { 0.2, 0.15, 0.2, 1.0 };

  GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };

  GLfloat direction[] = { 0.2, 1.0, 0.5, 0.0 };



  glMaterialfv(GL_FRONT, GL_SPECULAR, white);

  glMaterialf(GL_FRONT, GL_SHININESS, 30);



  glLightfv(GL_LIGHT0, GL_AMBIENT, dark);

  glLightfv(GL_LIGHT0, GL_DIFFUSE, white);

  glLightfv(GL_LIGHT0, GL_SPECULAR, white);

  glLightfv(GL_LIGHT0, GL_POSITION, direction);



  glEnable(GL_LIGHTING);                // so the renderer considers light

  glEnable(GL_LIGHT0);                  // turn LIGHT0 on

}



// Writes some trivial help text to the console.

void writeHelpToConsole() {

  std::cout << "j/l = roll left / right\n";

  std::cout << "i/k - pitch down / up\n";

  std::cout << "h/; - yaw left / right\n";

  std::cout << "8/m - speed up / slow down\n";

  std::cout << "w - toggle wireframe mode\n";

  std::cout << "r - generate a new landscape\n";

}



// main(): Initialize GLUT and enter the GLUT event loop.

int main(int argc, char** argv) {

  writeHelpToConsole();

  glutInit(&argc, argv);

  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

  glutInitWindowPosition(80, 80);

  glutInitWindowSize(780, 500);

  glutCreateWindow("Simple Flight");

  glutReshapeFunc(reshape);

  glutTimerFunc(100, timer, 0);

  glutDisplayFunc(display);

  glutKeyboardFunc(keyboard);

  init();

  glutMainLoop();

}